#include "tr_definitions.h"

static VECTOR dest_nodes;	// list of known destination nodes
static int idle_node_pos = 0;	// position of idle node

/*
 * TR_ack_timeout_inc(): Increase ACK timeout for destination node if possible
 */
void TR_ack_timeout_inc(NODE *n) {
  if (n->ack_timeout < (CnetTime)TR_ACK_TIMEOUT_MAX)
    n->ack_timeout += (CnetTime)TR_ACK_TIMEOUT_INC;
}

/*
 * TR_ack_timeout_dec(): Decrease ACK timeout for destination node if possible
 */
void TR_ack_timeout_dec(NODE *n) {
  if (n->ack_timeout > (CnetTime)(TR_ACK_TIMEOUT_MIN + 
    TR_ACK_TIMEOUT_PENALTY * TR_ACK_TIMEOUT_INC))
    n->ack_timeout -= (CnetTime)(TR_ACK_TIMEOUT_PENALTY * TR_ACK_TIMEOUT_INC);
}

/*
 * TR_attach_ack(): Attach waiting ACK info to packet header
 */
void TR_attach_ack(PACKET *pkt) {
  // next ACK waiting for transmission to this destination
  ACK_PENDING *ack_pending = remove_next_ack(get_node((CnetAddr)pkt->dest, &dest_nodes));
  if (ack_pending != NULL) {	// if ACK waiting
    pkt->seq_ack = ack_pending->seq_ack;	// set sequence number of ACK
    pkt->ack = ack_pending->ack;	// set ACK / NAK
    free(ack_pending);		// deallocate memory
  } else {			// no ACK waiting
    pkt->seq_ack = NOACKINFO;	// set identifier for no ACK
    pkt->ack = NAK;		// set default ACK
  }
}

/*
 * TR_retransmit_packet(): Prepare packet for retransmission and resend
 */
void TR_retransmit_packet(PACKET *pkt, PACKET_ID *pkt_id) {
  NODE *n = get_node((CnetAddr)pkt->dest, &dest_nodes);
  TR_attach_ack(pkt);		// attach new ACK info to packet
  pkt->crc_head = head_crc(pkt);	// compute new header CRC
  pkt_id->tid = CNET_start_timer(TR_ACK_TIMER, (CnetTime)n->ack_timeout, 
				 (CnetData)pkt_id);	// restart timer
  pkt_id->enabled = true;
  NW_route_packet(pkt);		// retransmit packet
}

/*
 * TR_confirm_packet(): Register receipt of ACK/NAK
 */
void TR_confirm_packet(const CnetAddr dest, const uint16_t seq, const bool ack) {
  NODE *n = get_node(dest, &dest_nodes);	// node information for destination
  WINDOW *w = (n->window);	// transport window for destination
  PACKET *pkt;			// packet in window
  PACKET_ID *pkt_id;		// packet ACK timer identifier
  
  if (n == NULL) {
    n = new_node(dest, &dest_nodes);
  }

  w = (n->window);

  if (ack) {			// if we got an ACK
    pkt = remove_packet(seq, w);	// remove packet from window
    pkt_id = remove_id(seq, w);
  } else {			// else it was a NAK
    pkt = get_packet(seq, w);	// get packet but don't remove
    pkt_id = get_id(seq, w);	// get packet ID but don't remove
  }
//  fprintf(stderr, "%s TR_confirm_packet(): pkt=0x%x, pkt_id=0x%x\n", nodeinfo.nodename, (unsigned int)pkt, (unsigned int)pkt_id);
  
  if (pkt_id != NULL && pkt != NULL) {		// if waiting for this acknowledgment
	if(pkt_id->enabled)
	{
		pkt_id->enabled = false;
		//CNET_stop_timer(pkt_id->tid);	// cancel timer
    }
    
    if (ack) {
      TR_ack_timeout_inc(n);	// increase ACK timeout
      free(pkt);		// deallocate packet
      free(pkt_id);		// deallocate packet identifier
      if (window_free(w))	// if window can take a new message
        CNET_enable_application(dest);	// enable messages for this destination
    } else {
      TR_ack_timeout_dec(n);	// decrease ACK timeout
      TR_retransmit_packet(pkt, pkt_id);	// retransmit packet
    }
  }
}

/*
 * TR_start_ack_timer(): Start ACK timer for packet
 */
void TR_start_ack_timer(PACKET *pkt) {
  NODE *n = get_node(pkt->dest, &dest_nodes);
  WINDOW *w = (n->window);
  PACKET_ID *pkt_id = malloc(sizeof(PACKET_ID));;
  
  pkt_id->addr = (CnetAddr)pkt->dest;	// set address of destination
  pkt_id->seq = pkt->seq;	// set sequence number
  add_id(pkt_id, w);	// add ID to list
  pkt_id->tid = CNET_start_timer(TR_ACK_TIMER, (CnetTime)n->ack_timeout, 
				(CnetData)pkt_id);	// start and save timer
	pkt_id->enabled = true;
  add_id(pkt_id, w);
}

/*
 * TR_idle_ack(): Send an ACK without piggybacking if possible
 */
static void TR_idle_ack(void) {
  NODE *n;			// node to send ACK to
  PACKET pkt;			// new packet to send
  ACK_PENDING *ack_pending;	// pending acknowledgment to send
  size_t len;
  int old_pos = idle_node_pos,	// save start position
      node_cnt = vector_nitems(dest_nodes);	// number of nodes in list
  
  if (idle_node_pos >= node_cnt)	// reset if at end of node list
    idle_node_pos = 0;
  
  for (int i = 0; i < node_cnt; i++) {	// iterate across nodes
    n = vector_peek(dest_nodes, (old_pos + i) % node_cnt, &len);	// starting from last position
    ack_pending = remove_next_ack(n);	// get next pending acknowledgment
    idle_node_pos = i + 1;	// save list position
    if (ack_pending != NULL) {	// if ACK waiting
      pkt.src = (unsigned char)(nodeinfo.address & 0xFF);	// set source address
      pkt.dest = (unsigned char)(n->addr & 0xFF);	// set destination address
      pkt.seq = 0;		// special ACK packet
      pkt.seq_ack = ack_pending->seq_ack;	// set sequence number of ACK
      pkt.ack = ack_pending->ack;	// set ACK / NAK
      free(ack_pending);		// deallocate memory
      pkt.len = 0;		// no message data
      pkt.crc_head = head_crc(&pkt);	// calculate header crc
      NW_route_packet(&pkt);	// transmit packet
      break;
    }
  }
}

/*
 * TR_ack_timeouts(): Handler for ACK timeout event - retransmit packet if not ACK'd
 */
void TR_ack_timeouts(CnetEvent ev, CnetTimerID timer, CnetData data) {
  PACKET_ID *pkt_id = (PACKET_ID *)data;	// retrieve packet ID
  pkt_id->enabled = false;
  NODE *n = get_node(pkt_id->addr, &dest_nodes);	// get destination node
  PACKET *pkt = get_packet(pkt_id->seq, (n->window));	//retrieve packet from window
  if (pkt != NULL) {
    TR_ack_timeout_dec(n);	// decrease ACK timeout
    TR_retransmit_packet(pkt, pkt_id);		// retransmit packet
  }
}

/*
 * TR_process_msg(): Encapsulate message in packet, pass to Network Layer
 */
void TR_process_msg(CnetAddr dest, MSG *msg, size_t len) {
  NODE *n = get_node(dest, &dest_nodes);	// node information for destination
  WINDOW *w;			// transport window for destination
  PACKET pkt;			// packet to be sent
  
  if (n == NULL) {		// if node not found
    n = new_node(dest, &dest_nodes);	// create new
  }
  w = (n->window);		// get window for node
  
  // set packet header fields
  pkt.src = (unsigned char)nodeinfo.address;	// set source address
  pkt.dest = (unsigned char)((uint32_t)dest & 0xFF);	// set dest address
  pkt.seq = w->next_seq++;	// set sequence number
  pkt.len = len;		// set message length
  TR_attach_ack(&pkt);		// add ACK info
  pkt.crc_head = head_crc(&pkt);	// calculate & store header CRC
  
  memcpy(&(pkt.msg), msg, len);	// copy message into packet
  pkt.crc_msg = msg_crc(&pkt);	// calculate and store packet CRC
  
  TR_start_ack_timer(&pkt);
  NW_route_packet(&pkt);	// pass packet to Network Layer for sending
  
  add_packet(&pkt, w);		// add packet to transport window
  
  if (window_full(w)) {		// if transport window now full
    CNET_disable_application(dest);	// disable messages for this destination
  }
}

/*
 * TR_msg_read(): Read message from Application Layer
 * Event handler for EV_APPLICATIONREADY
 */ 
static void TR_msg_read(CnetEvent ev, CnetTimerID timer, CnetData data) {
  CnetAddr dest;		// message destination address
  MSG msg;			// message data
  size_t len = sizeof(MSG);	// length of message data
  
  // read message from application layer
  CHECK(CNET_read_application(&dest, (char *)&msg, &len));
  
  TR_process_msg(dest, &msg, len);	// encapsulate message in packet
}
